Jerry's Log

Embedding in JAVA

contents

클린 코드(Clean Code) 를 작성하고 데이터베이스 엔티티 설계에서도 객체 지향 원칙을 지키기 위한 아주 훌륭한 기능인 @Embedded@Embeddable에 대해 알아보겠습니다.

보통 초보적인 데이터베이스 설계를 보면 User 테이블 하나에 컬럼이 50개씩 있는 경우가 있습니다. 그중 10개는 "집 주소"(도로명, 시, 우편번호...)이고, 또 다른 10개는 "직장 주소"인 식이죠.

@Embeddable@Embedded를 사용하면, 이렇게 연관된 필드들을 하나의 재사용 가능한 자바 클래스로 묶으면서도, 데이터베이스상에서는 여전히 하나의 테이블로 유지할 수 있습니다.


1. 핵심 개념: "값 타입 (Value Types)"

도메인 주도 설계(DDD)에서는 엔티티(Entity)와 값 객체(Value Object)를 구분합니다.

JPA는 이러한 값 객체를 매핑하기 위해 다음 두 애노테이션을 사용합니다.


2. 매핑 구조 시각화

여기서의 마법은 자바에서는 클래스가 두 개지만, 데이터베이스에는 오직 하나의 테이블만 존재한다는 점입니다.


3. 단계별 구현 방법

1단계: 임베더블 클래스 만들기 (@Embeddable)

이 클래스에는 @Entity@Id가 없습니다.

import jakarta.persistence.Embeddable;

@Embeddable // 전체가 아닌 '부품'임을 표시
public class Address {
    private String street;
    private String city;
    private String zipCode;

    // Getter, Setter, 기본 생성자 필수
}

2단계: 부모 엔티티에서 사용하기 (@Embedded)

import jakarta.persistence.Entity;
import jakarta.persistence.Embedded;
import jakarta.persistence.Id;

@Entity
public class User {
    
    @Id
    private Long id;
    private String name;

    @Embedded // Address의 컬럼들을 이 테이블에 끼워 넣음
    private Address homeAddress;
}

결과로 생성되는 SQL 테이블 (User):

id name street city zip_code
1 철수 테헤란로 123 서울 12345

4. "컬럼명 중복" 문제 (@AttributeOverrides)

만약 사용자에게 homeAddress(집 주소)와 workAddress(직장 주소), 이렇게 두 개의 주소가 있다면 어떻게 될까요?

단순히 @Embedded 필드를 두 개 넣으면, 하이버네이트는 street, city, zip_code라는 이름의 컬럼을 한 테이블에 두 번 만들려고 시도합니다. 당연히 컬럼 이름 중복 에러(Duplicate Column Name Error) 가 발생합니다.

해결책: 둘 중 적어도 하나의 컬럼 이름들을 재정의(Override)해줘야 합니다.

@Entity
public class User {
    @Id
    private Long id;

    // 기본 컬럼명 사용 (street, city, zip_code)
    @Embedded
    private Address homeAddress;

    // 컬럼명 재정의 (work_street, work_city, work_zip)
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "street", column = @Column(name = "work_street")),
        @AttributeOverride(name = "city", column = @Column(name = "work_city")),
        @AttributeOverride(name = "zipCode", column = @Column(name = "work_zip"))
    })
    private Address workAddress;
}

5. @Embedded vs. @OneToOne

면접에서 아주 자주 나오는 질문입니다. 둘 다 코드를 두 개의 클래스로 나눌 수 있게 해주는데, 차이점이 뭘까요?

특징 @Embedded @OneToOne
DB 구조 1개의 테이블 (컬럼이 합쳐짐). 2개의 테이블 (외래 키로 조인).
성능 더 빠름 (조인(JOIN) 불필요). 상대적으로 느림 (조인 필요).
정체성(Identity) 별도 ID 없음. 부모가 죽으면 같이 삭제됨. 별도 ID 있음. 독립적으로 존재 가능.
Null 처리 모든 필드가 null이면 객체 자체가 null이 됨. 객체 자체가 null일 수 있음.
재사용 여러 User가 하나의 Address 객체를 공유 불가. 공유 가능 (두 유저가 한 주소를 가리킴).

6. 언제 무엇을 써야 할까요?

개발자를 위한 요약

  1. 더 깔끔한 코드: User 클래스가 100개의 필드를 가진 "신(God Class)"이 되는 것을 막아줍니다.
  2. 재사용성: Address 클래스를 한 번만 잘 만들어두면 User, Company, Order 등 모든 엔티티에서 필드를 다시 작성할 필요 없이 재사용할 수 있습니다.
  3. 성능 비용 없음: DB 입장에서는 어차피 하나의 테이블이므로, 필드를 엔티티에 직접 적는 것과 성능 차이가 전혀 없습니다.

references